package quic

import (
	"github.com/lucas-clemente/quic-go/internal/protocol"
	"errors"
	"github.com/lucas-clemente/quic-go/fec"
	"github.com/lucas-clemente/quic-go/internal/wire"
)

/**
 *	Receives payloads for a block (FEC Group) and gives it to the FEC scheme. FEC scheme returns :
 * 		- One or more FEC Repair packet payloads
 * 		- FEC Payload IDs for the FEC Repair packet payloads (in a same group, these IDs will be the FEC Group ?)
 *  Then, the FEC Framework adds the FEC Group to the ADUs (quic packets) and returns it. Then, it stores FEC repair
 * 	payloads with their FEC Payload ID (FEC Group) to then construct FEC Frames.
 */


//		+----------------------+
//		|     Application      |
//		+----------------------+
//		|
//		|(1) ADUs
//		|
//		v
//		+----------------------+                           +----------------+
//		|    FEC Framework     |                           |                |
//		|                      |-------------------------->|   FEC Scheme   |
//		|(2) Construct source  |(3) Source Block           |                |
//		|    blocks            |                           |(4) FEC Encoding|
//		|(6) Construct FEC     |<--------------------------|                |
//		|    source packets and|                           |                |
//		|    repair payloads   |(5) Repair FEC Payload ID  |                |
//		+----------------------+    Repair symbols         +----------------+
//		|             |
//		|             |
//		|             |
//		|(7) Source   |(7') Repair payloads (FEC Frames)
//		|    packets  |
//		|             |
//		|      + -- -- -- -- -+
//		|      |     QUIC     |
//		|      +-- -- -- -- --+
//		v             v
//		+----------------------+
//		|   Transport Layer    |
//		|     (e.g., UDP)      |
//		+----------------------+

//  	Figure 3: Sender Operation with QUIC FEC Framework


type FECFrameworkSender struct {
	fecScheme    				 fec.FECScheme
	fecScheduler				 fec.FECScheduler
	fecFramer   				 *FECFramer
	redundancyController fec.RedundancyController

	nextFECPayloadID		 protocol.FECPayloadID
	fecWindow						 *fec.FECWindow
}

var FECFrameworkSenderPacketHandledWithWrongFECGroup = errors.New("FECFrameworkSender: A packet with the wrong FEC Group Number has been added")
// TODO define a window size and a spacing
// TODO: define convolutional design in FEC frames
func NewFECFrameworkSender(fecScheme fec.FECScheme, fecScheduler fec.FECScheduler, fecFramer *FECFramer, redundancyController fec.RedundancyController, version protocol.VersionNumber) *FECFrameworkSender {
	window := fec.NewFECWindow(uint8(10), 2, version)

	return &FECFrameworkSender{
		fecScheme: 		fecScheme,
		fecScheduler: fecScheduler,
		fecFramer: 		fecFramer,
		redundancyController: redundancyController,
		nextFECPayloadID: 1,
		fecWindow: 		window,
	}
}

// handles this packet for Forward Error Correction.
// Returns the packet as it should be sent
// TODO: the framework should receive a packet without a valid FEC Group and should insert it itself.
func (f *FECFrameworkSender) HandlePacket(packet []byte, hdr *wire.Header) ([]byte, error) {
	//TODO: decide if we here assume that the packet already has the correct header, fecGroupNumber or not
	if hdr.FECGroup != f.GetNextFECGroupNumber() {
		return nil, FECFrameworkSenderPacketHandledWithWrongFECGroup
	}
	var fecContainer fec.FECContainer
	if _, ok := f.fecScheme.(fec.ConvolutionalFECScheme); ok {
		fecContainer = f.fecWindow
	} else {
		fecContainer = f.fecScheduler.GetNextFECGroup()
	}
	fecContainer.AddPacket(packet, hdr)
	// TODO: remove these ugly ifs
	if fecContainer.ShouldBeSent(f.redundancyController) {
		if _, ok := f.fecScheme.(fec.ConvolutionalFECScheme); ok {

		} else {
			fecGroup := fecContainer.(*fec.FECGroup)
			fecGroup.TotalNumberOfPackets = fecContainer.CurrentNumberOfPackets()
			defer f.fecScheduler.SentFECGroup(fecGroup.FECGroupNumber)
		}
		f.GenerateRepairSymbols(fecContainer, f.redundancyController.GetNumberOfRepairSymbols(), f.nextFECPayloadID)
		fecContainer.PrepareToSend()	// will never error thanks to the ShouldBeSent check
		f.fecFramer.pushRepairSymbols(fecContainer.GetRepairSymbols())
	}
	f.nextFECPayloadID++
	return packet, nil
}

func (f *FECFrameworkSender) GetNextFECGroupNumber() protocol.FECGroup {
	if _, ok := f.fecScheme.(fec.ConvolutionalFECScheme); ok {
		return protocol.FECGroup(f.nextFECPayloadID>>8)
	}
	return f.fecScheduler.GetNextFECGroupNumber()
}

func (f *FECFrameworkSender) GetNextFECGroupOffset() byte {
	if _, ok := f.fecScheme.(fec.ConvolutionalFECScheme); ok {
		return byte(f.nextFECPayloadID)
	}
	return f.fecScheduler.GetNextFECGroupOffset()
}

func (f *FECFrameworkSender) GenerateRepairSymbols(container fec.FECContainer, numberOfSymbols uint, id protocol.FECPayloadID) error {
	symbols, err := f.fecScheme.GetRepairSymbols(container, numberOfSymbols, id)

	if err != nil {
		return err
	}
	container.SetRepairSymbols(symbols)
	return nil
}